package com.xdf.springsecuritydemo.config;

import com.xdf.springsecuritydemo.handler.MyAccessDeniedHandler;
import com.xdf.springsecuritydemo.handler.MyAuthenticationFailureHandler;
import com.xdf.springsecuritydemo.handler.MyAuthenticationSuccessHandler;
import com.xdf.springsecuritydemo.service.security.UserDetailServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;
import javax.sql.DataSource;

/**
 * @author chanchaw
 * @create 2025-09-21 9:20
 */
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    private UserDetailServiceImpl userDetailService;
    @Autowired
    private PersistentTokenRepository persistentTokenRepository;
    @Autowired
    private MyAccessDeniedHandler myAccessDeniedHandler;
    @Value("${server.servlet.context-path}")
    private String contextPath;
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.formLogin()// 表单提交
                .usernameParameter("employeeId")// 采用自定义的属性名，替代SpringSecurity默认的username
                .passwordParameter("password")
//                .loginPage("/mylogin.html")// 自定义登录页面
                .loginPage("/sign/thymeleaflogin")

                // 登录请求该URI，不需要在控制器中制作该请求
                // 会自动调用实现 UserDetailsService 的自定义类的方法 loadUserByUsername
                // 根据下面定义的URI，前端页面应该请求：/ssdemo/sign/login
                // 其中 ssdemo 是项目根URI
                .loginProcessingUrl("/sign/login")

                //.successForwardUrl("/sign/loginSuccess")// 验证用户名+密码成功后执行的URI，跳转到本项目内的其他页面
                //.failureForwardUrl("/sign/loginErr")// 跳转到登录失败页面

                // 下面登录成功跳转的目标页面不可：/index.html，会访问地址：http://localhost:8082/index.html，忽略了项目名称 ssdemo
                // 也不可以：index.html，由于是从登录页面跳转来的会拼接登录的URI，其是：/sign/login，那么登录成功后会跳转到：http://localhost:8082/sign/index.html
                // 即替换登录URI的最后一个path成为 /sign/index.html，实际的页面地址应该是：http://localhost:8082/ssdemo/index.html
                .successHandler(new MyAuthenticationSuccessHandler(contextPath + "/index.html"))// 使用自定义的登录成功处理器，通过重定向跳转到项目之外的互联网页面
                .failureHandler(new MyAuthenticationFailureHandler(contextPath + "/loginerr.html"))
        ;
        // 表单登录模式下必须设置，否则认证成功也无法登录成功
//        http.csrf().disable();

        // 授权，必须按照顺序配置，即无需权限验证的页面放在前面并使用permitAll
        // 如果将 .anyRequest().authenticated() 放在前面则由于没有权限直接验证失败退出
        // 实际如果这样做在运行项目时就会报错，SpringSecurity 不允许这样的顺序
        http.authorizeRequests()
                // 无需授权的路径
                .antMatchers("/mylogin.html","/loginerr.html","/sign/thymeleaflogin").permitAll()
                //.antMatchers("/css/**","/js/**","/images/**").permitAll() 按照路径放行
                .antMatchers("/**/*.js","/**/*.png","/**/*.jpg","/**/*.img","/**/*.css","/**/*.scss","/test/mvcMatchers","/sign/thymeleafdemo")
                .permitAll()
                //.mvcMatchers("/test/mvcMatchers").permitAll()// 放行无需权限的API

                // 要求至少拥有下面多个权限中的一个才能访问页面 main.html
                .antMatchers("/main.html").hasAnyAuthority("dev","admin1")

                // 针对同一个页面同时设置要求权限和角色
                // 虽然前面的权限验证失败，但是后面验证角色通过也可以访问
                //.antMatchers("/main.html").hasRole("factory")

                // 使用 access 表达式实现 hasRole 功能
                // 类似的 permitAll 和 hasAnyAuthority 以及其他的权限验证方法都可以使用 access 调用
                // 可通过读取 DB 配置灵活实现鉴权
                //.antMatchers("/main.html").access("hasRole('factory')")

                // 要求只有 127.0.0.1 能访问
                //.antMatchers("/main.html").hasIpAddress("127.0.0.1")

                // 其他所有路径要求授权
                .anyRequest().authenticated();
                // 使用自定义的鉴权方法替代上面的 .anyRequest().authenticated()
                // 自己的处理逻辑中没有自动跳转到登录也的逻辑（如果用户没有登录就直接访问项目内页面）
                //.anyRequest().access("@myAuthenticationServiceImpl.hasPermission(request)");

        // 使用自定义的权限不足处理器
        http.exceptionHandling().accessDeniedHandler(myAccessDeniedHandler);

        // remember-me
        http.rememberMe().tokenRepository(persistentTokenRepository)
                //.rememberMeParameter("rme") 自定义“记住我”在表单中的属性名称，类似自定义 username 属性名称
                .tokenValiditySeconds(60)// 记住我的保持时长，单位：秒，即60秒内记住我，超时则需要重新输入账号密码登录
                .userDetailsService(userDetailService)// 使用自定义的登录验证逻辑，否则 remember-me 不生效
        ;

        http.logout().logoutUrl("/sign/out");
    }
}
